package com.chinazyjr.framework.common.component.impl;

import java.rmi.registry.LocateRegistry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.alibaba.druid.stat.DruidStatService;
import com.alibaba.dubbo.common.utils.NetUtils;
import com.chinazyjr.framework.common.component.JmxRegisterService;
import com.chinazyjr.framework.common.component.ZookeeperService;
import com.chinazyjr.framework.common.config.HConfig;
import com.sys.midware.zookeeper.curator.ZKRegistryCenter;

@Configuration
public class JmxRegisterServiceImpl implements JmxRegisterService {

    private static final Logger LOGGER = LoggerFactory.getLogger(JmxRegisterService.class);

    private static final String JMX_ZOOKEEPER_NAMESPACE = "jmx";

    private static final String JMX_USERNAME = "jmx.username";

    private static final String JMX_PASSWORD = "jmx.password";

    private static final String JMX_REGISTER_PORT = "jmx.register.port";

    private static final String JMX_REGISTER_ZOOKEEPER_NAME = "jmx.register.zookeeper.name";

    private static final String DUBBO_MONITOR = "dubbo_monitor";

    @Autowired
    ZookeeperService zookeeperService;

    @Bean(name = "mbeanServer")
    public MBeanServer createMBeanServer() throws Exception {
        // 端口号
        Integer port = HConfig.CONTEXT.getPropertyInt(JMX_REGISTER_PORT);
        String serverName = HConfig.CONTEXT.getProperty(JMX_REGISTER_ZOOKEEPER_NAME);
        String user = HConfig.CONTEXT.getProperty(JMX_USERNAME);
        String pw = HConfig.CONTEXT.getProperty(JMX_PASSWORD);
        if (port == null || serverName == null) {
            return null;
        }
        LocateRegistry.createRegistry(port);
        MBeanServer server = MBeanServerFactory.createMBeanServer();
        String[] credentials = new String[] { user, pw };
        Map<String, Object> props = new HashMap<>();
        props.put("jmx.remote.credentials", credentials);
        props.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() {
            public Subject authenticate(Object credentials) {
                String[] sCredentials = (String[]) credentials;
                if (sCredentials == null || sCredentials.length < 2) {
                    throw new SecurityException("Authentication failed! ");
                }
                String userName = sCredentials[0];
                String password = sCredentials[1];
                if (userName.equals(user) && password.equals(pw)) {
                    Set<JMXPrincipal> principals = new HashSet<>();
                    principals.add(new JMXPrincipal(userName));
                    return new Subject(true, principals, Collections.EMPTY_SET, Collections.EMPTY_SET);
                } else
                    throw new SecurityException("Authentication failed! ");
            }
        });

        String localHost = NetUtils.getLocalHost();
        // props.put("com.sun.management.jmxremote.authenticate", new String[]
        // {"false"});
        props.put("com.sun.management.jmxremote.ssl", false);
        // supply the map of credentials to the connect call
        JMXServiceURL address = new JMXServiceURL(
                String.format("service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", localHost, port));
        JMXConnectorServer cserver = JMXConnectorServerFactory.newJMXConnectorServer(address, props, server);
        cserver.start();

        // ############################# druid
        // ########################################
        // 注册 druid 监控
        server.registerMBean(DruidStatService.getInstance(), new ObjectName(DruidStatService.MBEAN_NAME));
        LOGGER.info("start jmx server:{} port:{}", localHost, port);
        // 注册 druid监控服务 到zookeeper
        registerDruid(localHost, port);

        return server;
    }

    public void registerDruid(String ip, Integer port) {
        try {
            ZKRegistryCenter zk = zookeeperService.getZKClient(JMX_ZOOKEEPER_NAMESPACE);
            // 持久节点
            String persist = "/" + DUBBO_MONITOR + "/" + HConfig.CONTEXT.getProperty(JMX_REGISTER_ZOOKEEPER_NAME);
            boolean existed = zk.isExisted(persist);
            if (!existed) {
                zk.persist(persist);
                LOGGER.info("zookeeper persist:{}", persist);
            }
            String serverUrl = String.format("service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", ip, port).replace("/", "_");
            ;
            LOGGER.info("zookeeper persist_ephemeral:{}", serverUrl);
            // 连接不释放!
            zk.persistEphemeral(persist + "/" + serverUrl);
        } catch (Exception e) {
            LOGGER.error("error", e);
        }

    }

    @Override
    public List<Map<String, Object>> serverListDruid() {
        List<Map<String, Object>> data = new ArrayList<>();
        ZKRegistryCenter zk = zookeeperService.getZKClient(JMX_ZOOKEEPER_NAMESPACE);
        String persist = "/" + DUBBO_MONITOR;
        List<String> list = zk.getChildrenKeys(persist);
        for (String server : list) {
            Map<String, Object> map = new HashMap<>();
            map.put("server", server);
            List<String> addrs = zk.getChildrenKeys(persist + "/" + server);
            List<String> serverAddrs = new ArrayList<>();
            for (String addr : addrs) {
                serverAddrs.add(addr.replace("_", "/"));
            }
            map.put("addrs", serverAddrs);

            data.add(map);
        }
        return data;
    }

}
